﻿// <copyright file="CommonTests.cs" company="Sinbadsoft">
//        Copyright (c) Sinbadsoft 2009.
//        This file is released under the terms of the
//        GNU Library General Public License (LGPL) version 2.1
//        Please refer to the "License.txt" accompanying this file.
// </copyright>
// <author>Chaker NAKHLI</author>
// <email>Chaker.Nakhli@Sinbadsoft.com</email>
// <date>2009/04/17</date>

namespace Sinbadsoft.AdvancedCollections.Tests.ReversibleDictionary
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.IO;
    using System.Runtime.Serialization.Formatters.Binary;
    using System.Text;
    using MbUnit.Framework;

    internal delegate IReversibleDictionary<TKey, TValue> Factory_Delegate<TKey, TValue>(
        IEnumerable<KeyValuePair<TKey, TValue>> data);

    /// <summary>
    /// Fundamental tests for <see cref="IReversibleDictionary{TKey,TValue}"/> implementations.
    /// </summary>
    /// <typeparam name="TKey"> The type of the dictionary's key elements. </typeparam>
    /// <typeparam name="TValue"> The type of the dictionary's value elements. </typeparam>
    /// <remarks>
    /// This class adresses the testing of the contract defined by <see cref="IReversibleDictionary{TKey,TValue}"/>.
    /// A specific implementation of <see cref="IReversibleDictionary{TKey,TValue}"/> may need other tests for
    /// its specific implementation features (such as order, complexity, etc.).
    /// </remarks>
    internal class CommonTests<TKey, TValue>
    {
        private const string TESTPREFIX = "CommonTests - ";
        private Factory_Delegate<TKey, TValue> factory;
        private IEnumerable<KeyValuePair<TKey, TValue>> dictData;
        private IEnumerable<KeyValuePair<TKey, TValue>> testData;
        private KeyValuePair<TKey, TValue> testDataFirstPair;
        private KeyValuePair<TKey, TValue> testDataSecondPair;
        private string dictionaryTypeName;

        public CommonTests(
            Factory_Delegate<TKey, TValue> factory,
            IEnumerable<KeyValuePair<TKey, TValue>> dictData,
            IEnumerable<KeyValuePair<TKey, TValue>> testData)
        {
            this.factory = factory;
            this.dictData = dictData;
            this.testData = testData;
            this.dictionaryTypeName = "????";
        }

        private delegate void TestMethod();

        /// <summary>
        /// Asserts The given <see cref="IReversibleDictionary{TKey,TValue}"/> has the
        /// same elements and thes same elements order with its serialization/deserialization result.
        /// </summary>
        /// <typeparam name="TRDict"> The concerte type of the <see cref="IReversibleDictionary{TKey,TValue}"/>
        /// to test. </typeparam>
        /// <param name="rdict"> The <see cref="IReversibleDictionary{TKey,TValue}"/> instance to test. </param>
        /// <returns> The result of the serialization/deserialization operation. </returns>
        public static TRDict InMemorySerializeDeserialize<TRDict>(TRDict rdict)
            where TRDict : IReversibleDictionary<TKey, TValue>
        {
            using (MemoryStream fs = new MemoryStream())
            {
                BinaryFormatter bfmt = new BinaryFormatter();
                bfmt.Serialize(fs, rdict);
                fs.Seek(0, SeekOrigin.Begin);

                TRDict result = (TRDict)bfmt.Deserialize(fs);

                Assert.AreElementsEqual(rdict, result);
                Assert.AreElementsEqual(rdict.Reverse, result.Reverse);

                return result;
            }
        }

        public IEnumerable<Test> GenerateTests()
        {
            using (IEnumerator<KeyValuePair<TKey, TValue>> enumerator = this.testData.GetEnumerator())
            {
                enumerator.MoveNext();
                this.testDataFirstPair = enumerator.Current;
                enumerator.MoveNext();
                this.testDataSecondPair = enumerator.Current;
            }

            this.dictionaryTypeName = ToString(this.factory(this.testData).GetType());

            IList<Test> tests = new List<Test>();
            tests.Add(this.FromMethod(this.InitFromEnumerable));
            tests.Add(this.FromMethod(this.AddRemoveCount));
            tests.Add(this.FromMethod(this.ExceptionOnGetAbsentKeyOrValue));
            tests.Add(this.FromMethod(this.ExceptionOnAddPresentKeyOrValue));
            tests.Add(this.FromMethod(this.ExceptionOnNullInitData));
            tests.Add(this.FromMethod(this.ExceptionOnNullKeyOrValue));
            tests.Add(this.FromMethod(this.CopyTo));
            tests.Add(this.FromMethod(this.Clear));
            tests.Add(this.FromMethod(this.Indexer));

            tests.Add(this.FromMethod(this.NonGenericAddRemoveCount));
            tests.Add(this.FromMethod(this.NonGenericCopyTo));
            tests.Add(this.FromMethod(this.NonGenericClear));
            tests.Add(this.FromMethod(this.NonGenericExceptionOnKeyOrValueOfWrongType));
            tests.Add(this.FromMethod(this.NonGenericNoExceptionOnKeyOrValueOfWrongType));
            return tests;
        }

        protected void InitFromEnumerable()
        {
            IReversibleDictionary<TKey, TValue> rdict = this.factory(this.dictData);
            Assert.AreElementsEqualIgnoringOrder(this.dictData, rdict);
        }

        protected void AddRemoveCount()
        {
            IReversibleDictionary<TKey, TValue> rdict = this.factory(this.dictData);
            
            foreach (KeyValuePair<TKey, TValue> pair in this.testData)
            {
                int initialCount = rdict.Count;
                rdict.Add(pair.Key, pair.Value);
                CheckAddedGeneric(rdict, pair, initialCount);

                Assert.IsTrue(rdict.Remove(pair.Key));
                CheckRemovedGeneric(rdict, pair, initialCount);

                rdict[pair.Key] = pair.Value;
                CheckAddedGeneric(rdict, pair, initialCount);

                Assert.IsTrue(rdict.Remove(pair));
                CheckRemovedGeneric(rdict, pair, initialCount);

                rdict.Add(pair);
                CheckAddedGeneric(rdict, pair, initialCount);
            }
        }

        protected void CopyTo()
        {
            IReversibleDictionary<TKey, TValue> rdict = this.factory(this.dictData);
            KeyValuePair<TKey, TValue>[] copyHere = new KeyValuePair<TKey, TValue>[rdict.Count];
            rdict.CopyTo(copyHere, 0);
            Assert.AreElementsEqual(rdict, copyHere);
        }

        protected void Clear()
        {
            IReversibleDictionary<TKey, TValue> rdict = this.factory(this.dictData);
            Assert.IsNotEmpty(rdict);
            rdict.Clear();
            Assert.AreEqual(0, rdict.Count);
            Assert.AreEqual(0, rdict.Reverse.Count);
            Assert.IsEmpty(rdict);
            Assert.DoesNotThrow(delegate
            {
                rdict.Clear();
            });
        }

        protected void NonGenericClear()
        {
            IReversibleDictionary rdict = (IReversibleDictionary)this.factory(this.dictData);
            Assert.IsNotEmpty(rdict);
            rdict.Clear();
            Assert.AreEqual(0, rdict.Count);
            Assert.AreEqual(0, rdict.Reverse.Count);
            Assert.IsEmpty(rdict);
            Assert.DoesNotThrow(delegate
            {
                rdict.Clear();
            });
        }

        protected void ExceptionOnGetAbsentKeyOrValue()
        {
            IReversibleDictionary<TKey, TValue> rdict = this.factory(this.dictData);
            const int MAX_IT = 2;
            int iterCount = 0;
            foreach (KeyValuePair<TKey, TValue> pair in this.testData)
            {
                if (++iterCount > MAX_IT)
                {
                    break;
                }

                KeyValuePair<TValue, TKey> reversePair = new KeyValuePair<TValue, TKey>(pair.Value, pair.Key);
                DictionaryAssert.ThrowsOnGetItem(rdict, pair.Key);
                DictionaryAssert.ThrowsOnGetItem(rdict.Reverse, reversePair.Key);
            }
        }

        protected void ExceptionOnAddPresentKeyOrValue()
        {
            IReversibleDictionary<TKey, TValue> rdict = this.factory(this.dictData);
            const int MAX_IT = 2;
            int iterCount = 0;
            foreach (KeyValuePair<TKey, TValue> pair in rdict)
            {
                if (++iterCount > MAX_IT)
                {
                    break;
                }

                KeyValuePair<TValue, TKey> reversePair = new KeyValuePair<TValue, TKey>(pair.Value, pair.Key);
                DictionaryAssert.ThrowsOnAdd(rdict, pair);
                DictionaryAssert.ThrowsOnAdd(rdict.Reverse, reversePair);
            }
        }

        protected void ExceptionOnNullInitData()
        {
            Assert.Throws<ArgumentNullException>(delegate { this.factory(null); });
        }

        protected void Indexer()
        {
            IReversibleDictionary<TKey, TValue> rdict = this.factory(this.dictData);
            KeyValuePair<TKey, TValue> pair = this.testDataFirstPair;
            KeyValuePair<TKey, TValue> oldpair = this.testDataSecondPair;

            // case (1): key and val are not in the dictionary
            IndexerCase1(rdict, pair.Key, pair.Value, oldpair.Key, oldpair.Value);

            // case (2): (key, val) pair is in the dictionary
            IndexerCase2(rdict, pair.Key, pair.Value, oldpair.Key, oldpair.Value);

            // case (3): (key, oldVal) pair is in the dictionary
            IndexerCase3(rdict, pair.Key, pair.Value, oldpair.Key, oldpair.Value);

            // case (4): (oldKey, val) pair is in the dicitonary 
            IndexerCase4(rdict, pair.Key, pair.Value, oldpair.Key, oldpair.Value);

            // case (5): (oldKey, val) and (oldVal, key) pairs are in the dicitonary
            IndexerCase5(rdict, pair.Key, pair.Value, oldpair.Key, oldpair.Value);
        }

        protected void NonGenericExceptionOnKeyOrValueOfWrongType()
        {
            IReversibleDictionary rdict = (IReversibleDictionary)this.factory(this.dictData);

            // System.Collections.IDictionary.Add explicit impl
            Assert.Throws<ArgumentException>(
                delegate
                {
                    rdict.Add(new CustomPrivateType(), default(TValue));
                });
            Assert.Throws<ArgumentException>(
                delegate
                {
                    rdict.Add(default(TKey), new CustomPrivateType());
                });

            // System.Collections.IDictionary.Item explicit impl
            Assert.Throws<ArgumentException>(
                delegate
                {
                    rdict[new CustomPrivateType()] = default(TValue);
                });
            Assert.Throws<ArgumentException>(
                delegate
                {
                    object val = rdict[new CustomPrivateType()];
                });
            Assert.Throws<ArgumentException>(
               delegate
               {
                   rdict[default(TKey)] = new CustomPrivateType();
               });
        }

        protected void NonGenericNoExceptionOnKeyOrValueOfWrongType()
        {
            IReversibleDictionary rdict = (IReversibleDictionary)this.factory(this.dictData);
            Assert.DoesNotThrow(
                delegate
                {
                    rdict.Remove(new CustomPrivateType());
                });
            Assert.DoesNotThrow(
                delegate
                {
                    rdict.Contains(new CustomPrivateType());
                });
        }

        protected void NonGenericCopyTo()
        {
            IReversibleDictionary<TKey, TValue> rdict = this.factory(this.dictData);
            Array copyHereUntyped = new KeyValuePair<TKey, TValue>[rdict.Count];
            ((IReversibleDictionary)rdict).CopyTo(copyHereUntyped, 0);
            Assert.AreElementsEqual(rdict, (KeyValuePair<TKey, TValue>[])copyHereUntyped);
        }

        protected void NonGenericAddRemoveCount()
        {
            IReversibleDictionary rdict = (IReversibleDictionary)this.factory(this.dictData);

            foreach (KeyValuePair<TKey, TValue> pair in this.testData)
            {
                DictionaryEntry entry = new DictionaryEntry(pair.Key, pair.Value);
                int initialCount = rdict.Count;
                rdict.Add(pair.Key, pair.Value);
                CheckAddedNonGeneric(rdict, entry, initialCount);

                rdict.Remove(entry.Key);
                CheckRemovedNonGeneric(rdict, entry, initialCount);

                rdict[pair.Key] = pair.Value;
                CheckAddedNonGeneric(rdict, entry, initialCount);
            }
        }

        protected void ExceptionOnNullKeyOrValue()
        {
            IReversibleDictionary<TKey, TValue> rdict = this.factory(this.dictData);
            if (!typeof(TKey).IsValueType)
            {
                Assert.Throws<ArgumentNullException>(
                    delegate
                    {
                        rdict[default(TKey)] = this.testDataFirstPair.Value;
                    });
                Assert.Throws<ArgumentNullException>(
                    delegate
                    {
                        TValue val = rdict[default(TKey)];
                    });
                Assert.Throws<ArgumentNullException>(
                    delegate
                    {
                        rdict.Add(default(TKey), this.testDataFirstPair.Value);
                    });
            }

            if (!typeof(TValue).IsValueType)
            {
                Assert.Throws<ArgumentNullException>(
                    delegate
                    {
                        rdict[this.testDataFirstPair.Key] = default(TValue);
                    });
                Assert.Throws<ArgumentNullException>(
                    delegate
                    {
                        rdict.Add(this.testDataFirstPair.Key, default(TValue));
                    });
            }
        }

        private static void CheckAddedGeneric(
            IReversibleDictionary<TKey, TValue> rdict, 
            KeyValuePair<TKey, TValue> pair,
            int initialCount)
        {
            DictionaryAssert.Contains(rdict, pair);
            DictionaryAssert.Contains(rdict.Reverse, new KeyValuePair<TValue, TKey>(pair.Value, pair.Key));
            Assert.AreEqual(rdict.Reverse.Count, rdict.Count);
            Assert.AreEqual(initialCount + 1, rdict.Count);
        }

        private static void CheckRemovedGeneric(
            IReversibleDictionary<TKey, TValue> rdict,
            KeyValuePair<TKey, TValue> pair,
            int initialCount)
        {
            DictionaryAssert.DoesNotContainKey(rdict, pair.Key);
            DictionaryAssert.DoesNotContainKey(rdict.Reverse, pair.Value);
            Assert.AreEqual(rdict.Reverse.Count, rdict.Count);
            Assert.AreEqual(initialCount, rdict.Count);
            Assert.IsFalse(rdict.Remove(pair));
        }

        private static void CheckAddedNonGeneric(
            IReversibleDictionary rdict,
            DictionaryEntry entry,
            int initialCount)
        {
            Assert.IsTrue(rdict.Contains(entry.Key));
            Assert.IsTrue(rdict.Reverse.Contains(entry.Value));
            Assert.AreEqual(rdict.Reverse.Count, rdict.Count);
            Assert.AreEqual(initialCount + 1, rdict.Count);
        }

        private static void CheckRemovedNonGeneric(
            IReversibleDictionary rdict,
            DictionaryEntry entry,
            int initialCount)
        {
            Assert.IsFalse(rdict.Contains(entry.Key));
            Assert.IsFalse(rdict.Reverse.Contains(entry.Value));
            Assert.AreEqual(rdict.Reverse.Count, rdict.Count);
            Assert.AreEqual(initialCount, rdict.Count);
        }

        private static void IndexerCase1(
            IReversibleDictionary<TKey, TValue> dict,
            TKey key,
            TValue val,
            TKey oldKey,
            TValue oldVal)
        {
            dict[key] = val;
            EnsureNewValuesInAndOldValuesOut(dict, val, key, oldKey, oldVal);
        }

        private static void IndexerCase2(
            IReversibleDictionary<TKey, TValue> dict,
            TKey key,
            TValue val,
            TKey oldKey,
            TValue oldVal)
        {
            dict[key] = val;
            dict[key] = val;
            EnsureNewValuesInAndOldValuesOut(dict, val, key, oldKey, oldVal);
        }

        private static void IndexerCase3(
            IReversibleDictionary<TKey, TValue> dict,
            TKey key,
            TValue val,
            TKey oldKey,
            TValue oldVal)
        {
            dict[key] = oldVal;
            dict[key] = val;
            EnsureNewValuesInAndOldValuesOut(dict, val, key, oldKey, oldVal);
        }

        private static void IndexerCase4(
            IReversibleDictionary<TKey, TValue> dict,
            TKey key,
            TValue val,
            TKey oldKey,
            TValue oldVal)
        {
            dict[oldKey] = val;
            dict[key] = val;
            EnsureNewValuesInAndOldValuesOut(dict, val, key, oldKey, oldVal);
        }

        private static void IndexerCase5(
            IReversibleDictionary<TKey, TValue> dict,
            TKey key,
            TValue val,
            TKey oldKey,
            TValue oldVal)
        {
            dict[oldKey] = val;
            dict[key] = oldVal;
            dict[key] = val;
            EnsureNewValuesInAndOldValuesOut(dict, val, key, oldKey, oldVal);
        }

        private static void EnsureNewValuesInAndOldValuesOut(
            IReversibleDictionary<TKey, TValue> dict,
            TValue val,
            TKey key,
            TKey oldKey,
            TValue oldVal)
        {
            DictionaryAssert.Contains(dict, new KeyValuePair<TKey, TValue>(key, val));
            DictionaryAssert.Contains(dict.Reverse, new KeyValuePair<TValue, TKey>(val, key));

            DictionaryAssert.DoesNotContainKey(dict, oldKey);
            DictionaryAssert.DoesNotContainKey(dict.Reverse, oldVal);

            DictionaryAssert.DoesNotContain(dict, new KeyValuePair<TKey, TValue>(oldKey, val));
            DictionaryAssert.DoesNotContain(dict.Reverse, new KeyValuePair<TValue, TKey>(val, oldKey));
        }

        private static string ToString(Type t)
        {
            if (t.IsGenericType)
            {
                StringBuilder sbuilder = new StringBuilder(t.Name.Split('`')[0]);
                sbuilder.Append('<');
                Type[] genArgs = t.GetGenericArguments();
                for (int i = 0; i < genArgs.Length; i++)
                {
                    sbuilder.Append(ToString(genArgs[i]));
                    if (i < genArgs.Length - 1)
                    {
                        sbuilder.Append(',');
                    }
                }

                sbuilder.Append('>');
                return sbuilder.ToString();
            }

            return t.Name;
        }

        private TestCase FromMethod(TestMethod test)
        {
            return new TestCase(
                TESTPREFIX + test.Method.Name + " - " + this.dictionaryTypeName,
                delegate
                {
                    test();
                });
        }

        private class CustomPrivateType
        {
        }
    }
}
